/*
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 *    AlgVector.java
 *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
 *
 */

package weka.core;

import java.io.Serializable;
import java.util.Random;

/**
 * Class for performing operations on an algebraic vector of floating-point
 * values.
 * 
 * @author Gabi Schmidberger (gabi@cs.waikato.ac.nz)
 * @version $Revision: 1.10 $
 */
public class AlgVector implements Cloneable, Serializable, RevisionHandler {

	/** for serialization */
	private static final long serialVersionUID = -4023736016850256591L;

	/** The values of the matrix */
	protected double[] m_Elements;

	/**
	 * Constructs a vector and initializes it with default values.
	 * 
	 * @param n
	 *            the number of elements
	 */
	public AlgVector(int n) {

		m_Elements = new double[n];
		initialize();
	}

	/**
	 * Constructs a vector using a given array.
	 * 
	 * @param array
	 *            the values of the matrix
	 */
	public AlgVector(double[] array) {

		m_Elements = new double[array.length];
		for (int i = 0; i < array.length; i++) {
			m_Elements[i] = array[i];
		}
	}

	/**
	 * Constructs a vector using a given data format. The vector has an element
	 * for each numerical attribute. The other attributes (nominal, string) are
	 * ignored. Random is used to initialize the attributes.
	 * 
	 * @param format
	 *            the data format to use
	 * @param random
	 *            for initializing the attributes
	 * @throws Exception
	 *             if something goes wrong
	 */
	public AlgVector(Instances format, Random random) throws Exception {

		int len = format.numAttributes();
		for (int i = 0; i < format.numAttributes(); i++) {
			if (!format.attribute(i).isNumeric())
				len--;
		}
		if (len > 0) {
			m_Elements = new double[len];
			initialize(random);
		}
	}

	/**
	 * Constructs a vector using an instance. The vector has an element for each
	 * numerical attribute. The other attributes (nominal, string) are ignored.
	 * 
	 * @param instance
	 *            with numeric attributes, that AlgVector gets build from
	 * @throws Exception
	 *             if instance doesn't have access to the data format or no
	 *             numeric attributes in the data
	 */
	public AlgVector(Instance instance) throws Exception {

		int len = instance.numAttributes();
		for (int i = 0; i < instance.numAttributes(); i++) {
			if (!instance.attribute(i).isNumeric())
				len--;
		}
		if (len > 0) {
			m_Elements = new double[len];
			int n = 0;
			for (int i = 0; i < instance.numAttributes(); i++) {
				if (!instance.attribute(i).isNumeric())
					continue;
				m_Elements[n] = instance.value(i);
				n++;
			}
		} else {
			throw new IllegalArgumentException("No numeric attributes in data!");
		}
	}

	/**
	 * Creates and returns a clone of this object.
	 * 
	 * @return a clone of this instance.
	 * @throws CloneNotSupportedException
	 *             if an error occurs
	 */
	public Object clone() throws CloneNotSupportedException {

		AlgVector v = (AlgVector) super.clone();
		v.m_Elements = new double[numElements()];
		for (int i = 0; i < numElements(); i++) {
			v.m_Elements[i] = m_Elements[i];
		}

		return v;
	}

	/**
	 * Resets the elements to the default value which is 0.0.
	 */
	protected void initialize() {

		for (int i = 0; i < m_Elements.length; i++) {
			m_Elements[i] = 0.0;
		}
	}

	/**
	 * Initializes the values with random numbers between 0 and 1.
	 * 
	 * @param random
	 *            the random number generator to use for initializing
	 */
	protected void initialize(Random random) {

		for (int i = 0; i < m_Elements.length; i++) {
			m_Elements[i] = random.nextDouble();
		}
	}

	/**
	 * Returns the value of a cell in the matrix.
	 * 
	 * @param index
	 *            the row's index
	 * @return the value of the cell of the vector
	 */
	public final double getElement(int index) {
		return m_Elements[index];
	}

	/**
	 * Returns the number of elements in the vector.
	 * 
	 * @return the number of rows
	 */
	public final int numElements() {

		return m_Elements.length;
	}

	/**
	 * Sets an element of the matrix to the given value.
	 * 
	 * @param index
	 *            the elements index
	 * @param value
	 *            the new value
	 */
	public final void setElement(int index, double value) {

		m_Elements[index] = value;
	}

	/**
	 * Sets the elements of the vector to values of the given array. Performs a
	 * deep copy.
	 * 
	 * @param elements
	 *            an array of doubles
	 */
	public final void setElements(double[] elements) {

		for (int i = 0; i < elements.length; i++) {
			m_Elements[i] = elements[i];
		}
	}

	/**
	 * Gets the elements of the vector and returns them as double array.
	 * 
	 * @return an array of doubles
	 */
	public double[] getElements() {

		double[] elements = new double[this.numElements()];
		for (int i = 0; i < elements.length; i++) {
			elements[i] = m_Elements[i];
		}
		return elements;
	}

	/**
	 * Gets the elements of the vector as an instance. !! NON-numeric data is
	 * ignored sofar
	 * 
	 * @param model
	 *            the dataset structure to fit the data to
	 * @param random
	 *            in case of nominal values a random label is taken
	 * @return an array of doubles
	 * @throws Exception
	 *             if length of vector is not number of numerical attributes
	 */
	public Instance getAsInstance(Instances model, Random random)
			throws Exception {

		Instance newInst = null;

		if (m_Elements != null) {
			newInst = new Instance(model.numAttributes());
			newInst.setDataset(model);

			for (int i = 0, j = 0; i < model.numAttributes(); i++) {
				if (model.attribute(i).isNumeric()) {
					if (j >= m_Elements.length)
						throw new Exception("Datatypes are not compatible.");
					newInst.setValue(i, m_Elements[j++]);
				}
				if (model.attribute(i).isNominal()) {
					int newVal = (int) (random.nextDouble() * (double) (model
							.attribute(i).numValues()));
					if (newVal == (int) model.attribute(i).numValues())
						newVal -= 1;
					newInst.setValue(i, newVal);
				}
			}
		}
		return newInst;
	}

	/**
	 * Returns the sum of this vector with another.
	 * 
	 * @param other
	 *            the vector to add
	 * @return a vector containing the sum.
	 */
	public final AlgVector add(AlgVector other) {

		AlgVector b = null;

		if (m_Elements != null) {
			int n = m_Elements.length;
			try {
				b = (AlgVector) clone();
			} catch (CloneNotSupportedException ex) {
				b = new AlgVector(n);
			}

			for (int i = 0; i < n; i++) {
				b.m_Elements[i] = m_Elements[i] + other.m_Elements[i];
			}
		}

		return b;
	}

	/**
	 * Returns the difference of this vector minus another.
	 * 
	 * @param other
	 *            the vector to subtract
	 * @return a vector containing the difference vector.
	 */
	public final AlgVector substract(AlgVector other) {

		int n = m_Elements.length;
		AlgVector b;
		try {
			b = (AlgVector) clone();
		} catch (CloneNotSupportedException ex) {
			b = new AlgVector(n);
		}

		for (int i = 0; i < n; i++) {
			b.m_Elements[i] = m_Elements[i] - other.m_Elements[i];
		}

		return b;
	}

	/**
	 * Returns the inner (or dot) product of two vectors
	 * 
	 * @param b
	 *            the multiplication matrix
	 * @return the double representing the dot product
	 */
	public final double dotMultiply(AlgVector b) {

		double sum = 0.0;

		if (m_Elements != null) {
			int n = m_Elements.length;

			for (int i = 0; i < n; i++) {
				sum += m_Elements[i] * b.m_Elements[i];
			}
		}

		return sum;
	}

	/**
	 * Computes the scalar product of this vector with a scalar
	 * 
	 * @param s
	 *            the scalar
	 */
	public final void scalarMultiply(double s) {

		if (m_Elements != null) {
			int n = m_Elements.length;

			for (int i = 0; i < n; i++) {
				m_Elements[i] = s * m_Elements[i];
			}
		}
	}

	/**
	 * Changes the length of a vector.
	 * 
	 * @param len
	 *            the new length of the vector
	 */
	public void changeLength(double len) {

		double factor = this.norm();
		factor = len / factor;
		scalarMultiply(factor);
	}

	/**
	 * Returns the norm of the vector
	 * 
	 * @return the norm of the vector
	 */
	public double norm() {

		if (m_Elements != null) {
			int n = m_Elements.length;
			double sum = 0.0;

			for (int i = 0; i < n; i++) {
				sum += m_Elements[i] * m_Elements[i];
			}
			return Math.pow(sum, 0.5);
		} else
			return 0.0;
	}

	/**
	 * Norms this vector to length 1.0
	 */
	public final void normVector() {

		double len = this.norm();
		this.scalarMultiply(1 / len);
	}

	/**
	 * Converts a vector to a string
	 * 
	 * @return the converted string
	 */
	public String toString() {

		StringBuffer text = new StringBuffer();
		for (int i = 0; i < m_Elements.length; i++) {
			if (i > 0)
				text.append(",");
			text.append(Utils.doubleToString(m_Elements[i], 6));
		}

		text.append("\n");
		return text.toString();
	}

	/**
	 * Returns the revision string.
	 * 
	 * @return the revision
	 */
	public String getRevision() {
		return RevisionUtils.extract("$Revision: 1.10 $");
	}

	/**
	 * Main method for testing this class, can take an ARFF file as first
	 * argument.
	 * 
	 * @param args
	 *            commandline options
	 * @throws Exception
	 *             if something goes wrong in testing
	 */
	public static void main(String[] args) throws Exception {

		double[] first = { 2.3, 1.2, 5.0 };

		try {
			AlgVector test = new AlgVector(first);
			System.out.println("test:\n " + test);

		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
